﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace SpectationClient.DataBaseDescription {
    [Serializable()]
    public class TableInfo : Dictionary<String, ColumnInfo> {
        private String name;

        /// <summary>
        /// Gets or sets the SchemaInfo this tableName is related to.
        /// </summary>
        public SchemaInfo Schema { get; set; }
        public String Name { get { return this.name; } }
        /// <summary>

        public bool IsView { get; set;}
        
        /// The full-qualifying Schema and Table Name to be used in Npgsql-Commands
        /// e.g. "CORE"."SPECTRA" (instead of CORE.SPECTRA)
        /// </summary>
        public String SchemaTableName {
            get { return this.Schema != null ? String.Format("\"{0}\".\"{1}\"", this.Schema.Name, this.name) : null; }
        }

        public Key PrimaryKey { get {
                                    foreach(Key k in this.keys) {
                                        if(k.IsPrimaryKey) return k;
                                    }
                                    return null;
                                } }
        public bool hasPK {
            get {
                return this.PrimaryKey != null;
            } }
        private List<Key> keys = new List<Key>();
        
        private List<ForeignKey> foreignKeys = new List<ForeignKey>();
        public List<ForeignKey> FKeysForOtherTables {
            get { return this.foreignKeys.Where(k => k.SourceTable == this).ToList(); } 
        }
        
        public List<ForeignKey> FKeysFromOtherTables {
            get { return this.foreignKeys.Where(k => k.TargetTable == this).ToList(); }
        }

        public TableInfo(String tablename) {
            this.name = tablename;
        }

        new public void Add(String columnName, ColumnInfo columnInfo) {
            if(columnName != columnInfo.Name) throw new Exception("column key and column name do not match");
            this.Add(columnInfo);
        }
        public void Add(ColumnInfo columnInfo) {
            if(columnInfo.Table != null) {
                throw new Exception(
                    String.Format("ColumnInfo {0} already belongs to SchemaInfo {1}",
                    columnInfo.Name, columnInfo.Table.Name));
            } else {
                columnInfo.Table = this;
            }
            base.Add(columnInfo.Name, columnInfo);
        }
        

        public List<ColumnInfo> getColumns(List<String> columnNames) {
            return this.getColumns(columnNames.ToList());
        }
        public List<ColumnInfo> getColumns(String[] columnNames) {
            String[] notInTable = TableInfo.columnsNotInTable(this, columnNames);
            if(notInTable.Length > 0) throw new Exception(
                String.Format("Columns do not exist in tableName {0}: {1}", 
                              this.SchemaTableName, TextHelper.combine(notInTable, ",")
                              ));

            return (from ci in this.Values
                    where columnNames.Contains(ci.Name)
                    select ci).ToList();
        }

        public List<ColumnInfo> getColumns(Int16[] columnNumbers) {
            List<ColumnInfo> list=  new List<ColumnInfo>();
            foreach(Int16 n in columnNumbers) {
                list.Add(this.Values.ElementAt(n));
            }
            return list;
        }

      

        /// <summary>
        /// Marks the given columns as key.
        /// </summary>
        /// <param name="columnNames">Column Names</param>
        /// <param name="isPrimaryKey"></param>
        public void addKey(String[] columnNames, bool isPK) {
            String[] notInTable = TableInfo.columnsNotInTable(this, columnNames);
            if(notInTable.Length > 0) {
                throw new Exception(
                String.Format("Columns do not exists in TablesInfo {0}: {1}",
                              this.SchemaTableName, TextHelper.combine(notInTable, ","))
                );
            }

            List<ColumnInfo> keyColumns = (from ci in this.Values 
                                           where columnNames.Contains(ci.Name)
                                           select ci
                                          ).ToList();

            Key key = new Key(keyColumns, isPK);
            if(isPK) {
                this.keys.Insert(0, key);
            }else{
                this.keys.Add(key);
            }
            foreach(ColumnInfo c in keyColumns) {
                c.Key = key;
            }
        }

        /// <summary>
        /// Defines a new foreign key for this table.
        /// </summary>
        /// <param name="sourceColumns">The appendix columns that are part of an other Table Info</param>
        /// <param name="targetColums">The target columns - part of this Table info</param>
        public void addForeignKey(List<ColumnInfo> sourceColumns, List<ColumnInfo> targetColums) {
            //check if one of the new target columns was already defined as a target column
            foreach(ForeignKey fk in this.foreignKeys) {
                if(fk.TargetColumns.Intersect(targetColums).Count() > 0) {
                    //throw new Exception("Target column already defined as FK target");
                   // String stop = "";
                }
            }
            ForeignKey fkey = new ForeignKey(sourceColumns, targetColums);
            this.addForeignKey(fkey);
        }

        public void addForeignKey(ForeignKey foreignKey) { 
            if(!(foreignKey.SourceTable == this || foreignKey.TargetTable == this)){
                throw new Exception(String.Format("Foreign Key {0} has no relation to this table ({1})"
                                                 , foreignKey.ToString(), this.SchemaTableName));
            }

            this.foreignKeys.Add(foreignKey);
            if(foreignKey.TargetTable == this){
                foreach(ColumnInfo c in foreignKey.TargetColumns) {
                    c.ForeignKey = foreignKey;
                }
            }
        }
        

        public static String[] columnsNotInTable(TableInfo tableInfo, String[]columnNames) {
            return (from name in columnNames
                                   where !tableInfo.Keys.Contains(name)
                                   select name).ToArray();
            
        }

        public override string ToString() {
            return String.Format("TableInfo:{0}", this.Name);
        }

        public override bool Equals(object obj) {
            var ti = obj as TableInfo;
            return (ti == null) ? false : this.Equals(ti);
        }

        public bool Equals(TableInfo ti) {
            return this.SchemaTableName == ti.SchemaTableName;
        }

        public override int GetHashCode() {
            return this.SchemaTableName.GetHashCode();
        }

        public DataTable getEmptyTable() {
            DataTable dt = new DataTable(this.Name);
            foreach(ColumnInfo ci in this.Values) {
                dt.Columns.Add(ci.Name, ci.ValueType);
            }

            return dt;
        }

    }
}
